/*
 * Following codes(particularly check_secure_directory function) 
 * are derived from 
 * ``Secure Programming Cookbook for C and C++''.
 * URL:http://www.oreilly.com/catalog/secureprgckbk/ 
 *     http://www.secureprogramming.com/
 * 
 * Licensing Information can be obtain from following URL:
 *
 *  http://www.secureprogramming.com/?action=license
 *
 * Copyright  2003 by John Viega and Matt Messier.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met: 

 * 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the developer of this software nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <dirent.h>
#include <glib.h>
#include <openssl/bn.h>
#include <openssl/sha.h>
#include <openssl/md5.h>
#include <openssl/rsa.h>
#include <openssl/engine.h>
#include <openssl/err.h>
#include <openssl/rand.h>
#include <openssl/pem.h>
#include "common.h"

GStaticMutex dir_check_mutex = G_STATIC_MUTEX_INIT;
GStaticMutex file_check_mutex = G_STATIC_MUTEX_INIT;
GStaticMutex rsa_key_mutex = G_STATIC_MUTEX_INIT;

static RSA *rsa_keys[RSA_KEY_MAX];
static int key2ipmsg_key_type[]={
  IPMSG_RSA_2048,
  IPMSG_RSA_1024,
  IPMSG_RSA_512,
  -1};

static int
get_rsa_key_index(unsigned long cap,int *indp){
  int rc;
  int index;

  if ( (!indp) ||  (!(cap & RSA_CAPS)) )
    return -EINVAL;

  switch(cap)
    {
    case IPMSG_RSA_512:
      index=RSA_KEY_INDEX_512;
      break;
    case IPMSG_RSA_1024:
      index=RSA_KEY_INDEX_1024;
      break;
    case IPMSG_RSA_2048:
      index=RSA_KEY_INDEX_2048;
      break;
    default:
      g_assert_not_reached();
      break;
    }

  *indp=index;

  return 0;
}
static int
open_key_file_for_read(int *fd_p,const unsigned char *file) {
  int rc;
  int fd;
  uid_t uid;
  struct stat fbuff,lbuff;


  if ( (!fd_p) || (!file) )
    return -EINVAL;
  g_static_mutex_lock(&file_check_mutex); /* 厳密にいえば不要 */
  fd=open(file,O_RDONLY);
  if (fd<0) {
    rc=-errno;
    goto unlock_out;
  }
  rc=fstat(fd,&fbuff);
  if (rc<0) {
    rc=-errno;
    goto error_out;
  }
  rc=lstat(file,&lbuff);
  if (rc<0) {
    rc=-errno;
    goto error_out;
  }

  uid=geteuid();
  /*
   * 偏執的ではあるが, リンクでないことを確認する
   */
  rc=-EPERM;
  if ( (lbuff.st_mode != fbuff.st_mode) ||
       (lbuff.st_ino != fbuff.st_ino) ||
       (lbuff.st_dev != fbuff.st_dev) )
    goto error_out;
  /*
   * 本人以外に書き込めないことを確認する
   */
  if ( ((fbuff.st_mode) & RSA_DIR_INVALID_FLAGS))
    goto error_out;
  /*
   * rootが所有するファイルには, 書き込まない
   */
  if ( (fbuff.st_uid) && (fbuff.st_uid != uid) )
    goto error_out;

  rc=0;
  *fd_p=fd;

 unlock_out:
  g_static_mutex_unlock(&file_check_mutex); /* 厳密にいえば不要 */
  return rc;

 error_out:
  close(fd);
  g_static_mutex_unlock(&file_check_mutex); /* 厳密にいえば不要 */
  return rc;
}
static int
open_key_file_for_write(int *fd_p,const unsigned char *file) {
  int rc;
  int fd;
  uid_t uid;
  struct stat fbuff,lbuff;


  if ( (!fd_p) || (!file) )
    return -EINVAL;

  g_static_mutex_lock(&file_check_mutex); /* 厳密にいえば不要 */
  fd=open(file,O_CREAT|O_EXCL|O_WRONLY,S_IRUSR|S_IWUSR);
  if (fd<0){
    if (errno != EEXIST) {
      rc=-errno;
      goto unlock_out;
    }else{
      fd=open(file, O_WRONLY);
      if (fd<0){
	rc=-errno;
	goto unlock_out;
      }
    }
  }

  rc=fstat(fd,&fbuff);
  if (rc<0) {
    rc=-errno;
    goto error_out;
  }
  rc=lstat(file,&lbuff);
  if (rc<0) {
    rc=-errno;
    goto error_out;
  }

  uid=geteuid();
  /*
   * 偏執的ではあるが, リンクでないことを確認する
   */
  rc=-EPERM;
  if ( (lbuff.st_mode != fbuff.st_mode) ||
       (lbuff.st_ino != fbuff.st_ino) ||
       (lbuff.st_dev != fbuff.st_dev) )
    goto error_out;
  /*
   * 本人以外に書き込めないことを確認する
   */
  if ( ((fbuff.st_mode) & RSA_DIR_INVALID_FLAGS))
    goto error_out;
  /*
   * rootが所有するファイルには, 書き込まない
   */
  if ( (fbuff.st_uid) && (fbuff.st_uid != uid) )
    goto error_out;

  rc=0;
  *fd_p=fd;

 unlock_out:
  g_static_mutex_unlock(&file_check_mutex); /* 厳密にいえば不要 */
  return rc;

 error_out:
  close(fd);
  g_static_mutex_unlock(&file_check_mutex); /* 厳密にいえば不要 */
  return rc;
}
static int
check_secure_directory(const unsigned char *dir) {
  DIR *fd,*start;
  int rc;
  char new_dir[PATH_MAX+1];
  uid_t uid;
  struct stat fbuff,lbuff;
  void *ref;

  g_static_mutex_lock(&dir_check_mutex); /* 厳密にいえば不要 */
  start = opendir(".");
  if (!start) {
    rc=-errno;
    goto unlock_out;
  }
  rc=lstat(dir,&lbuff);
  if (rc<0) {
    rc=-errno;
    goto close_dir_out;
  }

  uid=geteuid();
  do{
    if (chdir(dir)) {
      rc=-errno;
      goto ret_to_work_dir_out;
    }
    fd=opendir(".");
    if (!fd)
      goto ret_to_work_dir_out;

    rc=fstat(dirfd(fd),&fbuff);
    closedir(fd);
    if (rc<0) {
      rc=-errno;
      goto ret_to_work_dir_out;
    }
    /*
     * 偏執的ではあるが, リンクでないことを確認する
     */
    rc=-EPERM;
    if ( (lbuff.st_mode != fbuff.st_mode) ||
	 (lbuff.st_ino != fbuff.st_ino) ||
	 (lbuff.st_dev != fbuff.st_dev) )
      goto ret_to_work_dir_out;
    /*
     * 本人以外に書き込めないことを確認する
     */
    if ( ((fbuff.st_mode) & RSA_DIR_INVALID_FLAGS))
      goto ret_to_work_dir_out;
    /*
     * rootが所有するファイルには, 書き込まない
     */
    if ( (fbuff.st_uid) && (fbuff.st_uid != uid) )
      goto ret_to_work_dir_out;
    /*
     *１つ上のディレクトリを探査する
     */
    dir="..";
    rc=lstat(dir,&lbuff);
    if (rc<0) {
      rc=-errno;
      goto ret_to_work_dir_out;
    }
    memset(new_dir,0,PATH_MAX+1);
    ref=getcwd(new_dir,PATH_MAX+1); /* 次に調査するディレクトリの
				    *  親ディレクトリを獲得
				    */
    if (!ref)
      goto ret_to_work_dir_out;
  }while(new_dir[1]);

  if (!new_dir[1])
    rc=0;
  else
    rc=-EPERM;

 ret_to_work_dir_out:
  fchdir(dirfd(start));
 close_dir_out:
  closedir(start);
 unlock_out:
  g_static_mutex_unlock(&dir_check_mutex); /* 厳密にいえば不要 */

  return rc;
}
static int
store_private_key(const char *fpath,RSA *rsa,pem_password_cb *cb){
  int rc;
  int fd;
  FILE *fp;
  EVP_CIPHER *enc=NULL;
  char errbuf[1024];

  if ( (!fpath) || (!rsa) )
    return -EINVAL;

  rc=open_key_file_for_write(&fd,fpath);
  if (rc)
    goto error_out;
  fp=fdopen(fd,"w");
  if (!fp) {
    rc=-errno;
    goto error_out;
  }

  if (cb) 
    enc=(EVP_CIPHER *)EVP_des_ede_cbc();
  
  rc=PEM_write_RSAPrivateKey(fp,rsa,enc,NULL,0,cb,NULL);  
  if (!rc){
    rc=ERR_get_error();
    err_out("Can not store private key %s : err=%s\n", fpath, ERR_error_string(rc, errbuf));
    rc=-rc;
  }

  rc=fclose(fp);
  if (rc)
    rc=-errno;

  rc=0;  

 error_out:
  return rc;
}
static int
store_public_key(const char *fpath,RSA *rsa){
  int rc;
  int fd;
  FILE *fp;
  EVP_CIPHER *enc=NULL;
  char errbuf[1024];

  if ( (!fpath) || (!rsa) )
    return -EINVAL;

  rc=open_key_file_for_write(&fd,fpath);
  if (rc<0)
    goto error_out;

  fp=fdopen(fd,"w");
  if (!fp) {
    rc=-errno;
    goto error_out;
  }
  
  rc=PEM_write_RSAPublicKey(fp,rsa);
  if (!rc){
    rc=ERR_get_error();
    err_out("Can not store public key %s : err=%s\n", fpath, ERR_error_string(rc, errbuf));
    rc=-rc;
  }

  rc=fclose(fp);
  if (rc)
    rc=-errno;

  rc=0;  

 error_out:
  return rc;
}

static int
load_private_key(const char *fpath,RSA **rsa,pem_password_cb *cb){
  int rc;
  int fd;
  FILE *fp;
  char errbuf[1024];
  void *ref;

  if ( (!fpath) || (!rsa) )
    return -EINVAL;

  rc=open_key_file_for_read(&fd,fpath);
  if (rc)
    goto error_out;

    
  fp=fdopen(fd,"r");
  if (!fp) {
    rc=-errno;
    goto error_out;
  }

  ref=PEM_read_RSAPrivateKey(fp,rsa,cb,NULL);  
  if (!ref){
    rc=ERR_get_error();
    err_out("Can not load private key %s : err=%s\n", fpath, ERR_error_string(rc, errbuf));
    rc=-rc;
  }

  rc=fclose(fp);
  if (rc)
    rc=-errno;

  rc=0;  

 error_out:
  return rc;
}
static int
load_public_key(const char *fpath,RSA **rsa){
  int rc;
  int fd;
  FILE *fp;
  EVP_CIPHER *enc=NULL;
  char errbuf[1024];
  RSA *ref;

  if ( (!fpath) || (!rsa) )
    return -EINVAL;

  rc=open_key_file_for_read(&fd,fpath);
  if (rc)
    goto error_out;

  fp=fdopen(fd,"r");
  if (!fp) {
    rc=-errno;
    goto error_out;
  }
  
  ref=PEM_read_RSAPublicKey(fp,rsa,NULL,NULL);
  if (!ref){
    rc=ERR_get_error();
    err_out("Can not read public key %s : err=%s\n", fpath, ERR_error_string(rc, errbuf));
    rc=-rc;
  }

  rc=fclose(fp);
  if (rc)
    rc=-errno;

  rc=0;  

 error_out:
  return rc;
}
static int 
generate_rsa_key(RSA **rsa_p,unsigned long key_type) {
  int rc;
  RSA *rsa;
  char errbuf[1024];
  int keylen;
  int retry_count=RSA_KEYGEN_RETRY;

  if (!rsa_p)
    return -EINVAL;

  rc=pcrypt_get_rsa_key_length(key_type,&keylen);
  if (rc)
    return rc;
  
  dbg_out("generate key length:%d\n",keylen);

  /* RSA 鍵生成 */
 retry:
  rsa = RSA_generate_key(keylen, RSA_F4, NULL, NULL);
  if ( rsa == NULL ){
    rc=ERR_get_error();
    dbg_out("in generate_key: err=%s\n", ERR_error_string(rc, errbuf));
    return (ERR_get_error()*-1);
  }
  if (RSA_check_key(rsa)>0){
    *rsa_p=rsa;
    rc=0;
  } else {
    RSA_free(rsa);
    rc=ERR_get_error();
    err_out("This is invalid key: err=%s\n", ERR_error_string(rc, errbuf));
    --retry_count;
    if (retry_count)
      goto retry;
  }

  return rc;
}
static int
convert_peer_key(const char *peer_e,const char *peer_n,RSA **rsa){
  int rc;
  RSA *pubkey=NULL;
  BIGNUM *bn_e=NULL,*bn_n=NULL;
  size_t size_in_byte;

  if ( (!peer_e) || (!peer_n) || (!rsa) )
    return -EINVAL;

  pubkey = RSA_new();
  if (!pubkey)
    return -ENOMEM;

  rc=-ENOMEM;
  bn_e = BN_new();
  if (!bn_e)
    goto free_pubkey_out;

  bn_n = BN_new();
  if (!bn_n)
    goto free_bn_e_out;

  rc=BN_hex2bn(&bn_e, peer_e);
  if (!rc)
    goto free_bn_e_out;

  rc=BN_hex2bn(&bn_n, peer_n);
  if (!rc)
    goto free_bn_e_out;

  pubkey->e = bn_e;
  pubkey->n = bn_n;
  *rsa=pubkey;

  
  return 0;

 free_bn_n_out:
  if (bn_n)
    BN_free(bn_n);
 free_bn_e_out:
  if (bn_e)
    BN_free(bn_e);
 free_pubkey_out:
  if (pubkey)
    RSA_free(pubkey);
  return rc;
}
static int
pcrypt_crypt_set_rsa_key(unsigned long cap,RSA *rsa) {
  int rc;
  int index;

  if ( (!(cap & RSA_CAPS)) || (!rsa) )
    return -EINVAL;

  rc=get_rsa_key_index(cap,&index);
  if (rc)
    return rc;

g_static_mutex_lock(&rsa_key_mutex);
  if (rsa_keys[index])
    RSA_free(rsa_keys[index]);
  
  rsa_keys[index]=rsa;
g_static_mutex_unlock(&rsa_key_mutex);

  return 0;
}
static int 
select_rsa_key_for_answer(unsigned long peer_cap,unsigned long *selected_key,int speed){
  unsigned long candidates;
  int i;
  int added_num=1;
  int first=0;
  int key_type;

  if (!selected_key)
    return -EINVAL;

  candidates=(get_asymkey_part(peer_cap) & hostinfo_get_ipmsg_crypt_capability());
  if (speed) {
    first=RSA_KEY_MAX-1;
    added_num=-1;
  }
  for (i=first;
       ((i>=0) && (i<RSA_KEY_MAX) && (!(key2ipmsg_key_type[i] & candidates)));
       i+=added_num);

  if (!( (i>=0) && (i<RSA_KEY_MAX) ))
    return -ENOENT;

  *selected_key=key2ipmsg_key_type[i];
  dbg_out("Selected key name in ipmsg:0x%x\n",key2ipmsg_key_type[i]);

  return 0;
}

int
pcrypt_get_rsa_key_length(unsigned long key_type,size_t *len){
  size_t keylen;
  int rc;

  if (!len)
    return -EINVAL;

  switch (key_type) {
  case IPMSG_RSA_512:
    keylen=512;
    break;
  case IPMSG_RSA_1024:
    keylen=1024;
    break;
  default: /* デフォルトは, 2048 */
  case IPMSG_RSA_2048:
    keylen=2048;
    break;
  }
  *len=keylen;
  rc=0;

  return rc;
}

int 
pcrypt_crypt_parse_anspubkey(const char *string,unsigned long *peer_cap,unsigned char **crypt_e,unsigned char **crypt_n){
  char *val_e=NULL,*val_n=NULL;
  char *sp=NULL;
  char *ep=NULL;
  char *buffer;
  char *hex;
  ssize_t remains;
  unsigned long cap;
  size_t len;
  int rc=0;

  if  ( (!string) || (!peer_cap) || (!crypt_e)  || (!crypt_n) )
    return -EINVAL;
  
  buffer=g_strdup(string);
  if (!buffer)
    return -ENOMEM;

  len=strlen(string);
  remains=len;
  /*
   * 暗号化能力
   */
  sp=buffer;
  ep=memchr(sp, ':', remains);
  if (!ep) {
    rc=-EINVAL;
    goto err_out;
  }

  *ep='\0';
  remains =len - ((unsigned long)ep-(unsigned long)buffer);
  if (remains<=0) {
    rc=-EINVAL;
    goto err_out;
  }
  ++ep;
  cap=(unsigned long)strtol(sp, (char **)NULL, 16);
  dbg_out("parsed capability:%x\n",cap);
  sp=ep;

  /*
   * 公開指数
   */
  ep=memchr(sp, '-', remains);
  if (!ep) {
    rc=-EINVAL;
    goto err_out;
  }
  *ep='\0';
  remains =len - ((unsigned long)ep-(unsigned long)buffer);
  if (remains<=0) {
    rc=-EINVAL;
    goto err_out;
  }
  ++ep;
  dbg_out("parsed public exponential:%s\n",sp);
  val_e=g_strdup(sp);
  if (!val_e) {
    rc = -ENOMEM;
    goto err_out;
  }
  sp=ep;

  /*
   *公開モジューロ(法)
   */
   dbg_out("parsed public modulo:%s\n",sp);
   val_n=g_strdup(sp);
   if (!val_n) {
    rc = -ENOMEM;
    dbg_out("Error code:%d\n",rc);
    goto err_out;
  }
  /*
   *返却
   */
  *peer_cap = cap;
  *crypt_e = val_e;
  *crypt_n = val_n;
  rc=0;
err_out:
  g_free(buffer);
  return rc;
}

int 
pcrypt_crypt_generate_anspubkey_string(unsigned long cap,const char **message_p){
  int rc;
  char *buffer;
  char *string;
  char *hex_e,*hex_n;
  RSA *rsa=NULL;
  unsigned long skey_type=0,akey_type=0,sign_type=0;

  if ( (!message_p) && (!(cap & RSA_CAPS)) )
    return -EINVAL;

  g_assert(!akey_type);
  rc=select_rsa_key_for_answer(cap,
			       &akey_type,
			       hostinfo_refer_ipmsg_crypt_policy_is_speed());
  if (rc)
    return rc;
  dbg_out("RSA key:%x\n",akey_type);

  g_assert(!skey_type);
  rc=select_symmetric_key(cap,
			  &skey_type,
			  hostinfo_refer_ipmsg_crypt_policy_is_speed());
  if (rc)
    return rc;
  dbg_out("sym key:%x\n",skey_type);

  g_assert(!sign_type);
  rc=select_signature(cap,&sign_type,hostinfo_refer_ipmsg_crypt_policy_is_speed());
  if (rc)
    dbg_out("No sign:%x\n",sign_type);
  else
    dbg_out("Sign type:%x\n",sign_type);
  rc=pcrypt_crypt_refer_rsa_key(akey_type,&rsa);
  if (rc){
    dbg_out("Can not find rsa key:%d\n",rc);
    return -EINVAL;
  }
  buffer=g_malloc(_MSG_BUF_SIZE);
  if (!buffer)
    return -ENOMEM;
  
  memset(buffer,0,_MSG_BUF_SIZE);
  rc=-ENOMEM;
  hex_e=BN_bn2hex(rsa->e);
  if (!hex_e)
    goto free_buffer_out;

  hex_n=BN_bn2hex(rsa->n);
  if (!hex_n)
    goto free_hex_n_out;
  dbg_out("hex-e:%s hex-n:%s\n",hex_e,hex_n);
  snprintf(buffer,_MSG_BUF_SIZE-1,"%x:%s-%s",(akey_type|skey_type|sign_type),hex_e,hex_n);
  dbg_out("Generated:%s\n",buffer);
  string=g_strdup(buffer);
  *message_p=string;
  rc=0;

 free_hex_n_out:
  if (hex_n)
    OPENSSL_free(hex_n);
 free_hex_e_out:
  if (hex_e)
    OPENSSL_free(hex_e);
 free_buffer_out:
  if (buffer)
    g_free(buffer);

  return rc;
}

int 
pcrypt_crypt_generate_getpubkey_string(unsigned long cap,const char **message_p){
  char *buffer;
  char *string;

  if (!message_p)
    return -EINVAL;

  buffer=g_malloc(_MSG_BUF_SIZE);
  if (!buffer)
    return -ENOMEM;

  memset(buffer,0,_MSG_BUF_SIZE);
  snprintf(buffer,_MSG_BUF_SIZE-1,"%x",cap);
  dbg_out("Generated:%s\n",buffer);
  string=g_strdup(buffer);
  *message_p=string;

  if (buffer)
    g_free(buffer);

  return 0;
}

int
pcrypt_crypt_refer_rsa_key_with_index(int index,RSA **rsa_p) {
  int rc;

  if ( (index<0) || (RSA_KEY_MAX<=index) )
    return -EINVAL;

  rc=0;
g_static_mutex_lock(&rsa_key_mutex);
  if (rsa_keys[index])
    *rsa_p=rsa_keys[index];
  else
    rc=-ENOENT;
g_static_mutex_unlock(&rsa_key_mutex);
  return rc;
}
int
pcrypt_crypt_refer_rsa_key(unsigned long cap,RSA **rsa_p) {
  int rc;
  int index;

  if (!(cap & RSA_CAPS))
    return -EINVAL;

  rc=0;
  rc=get_rsa_key_index(cap,&index);
  if (rc)
    return rc;

g_static_mutex_lock(&rsa_key_mutex);
  if (rsa_keys[index])
    *rsa_p=rsa_keys[index];
  else
    rc=-ENOENT;
g_static_mutex_unlock(&rsa_key_mutex);

  return rc;
}

int
pcrypt_crypt_init_keys(void) {
  int rc;
  int i;
  int keylen;
  RSA *rsa=NULL;

  ERR_load_crypto_strings();
   for(i=0;key2ipmsg_key_type[i]>=0;++i) {
     rc=pcrypt_load_rsa_key(key2ipmsg_key_type[i],NULL);
     if (!rc)
       continue; /* 鍵をロードした  */
     dbg_out("Can not load key:rc=%d\n",rc);
     rc=generate_rsa_key(&rsa,key2ipmsg_key_type[i]);
     if (rc) {
       rc=pcrypt_get_rsa_key_length(key2ipmsg_key_type[i],&keylen);
       if (!rc)
	 err_out("Can not generate key length:%d\n",keylen);
       else
	 err_out("Can not generate key length invalid key index:%d\n",i);
       rsa_keys[i]=NULL;
       continue;
     }
     rc=pcrypt_crypt_set_rsa_key(key2ipmsg_key_type[i],rsa);
     if (rc)
       err_out("Can not set key length:%d\n",keylen);
   }


   return 0;
}

int
pcrypt_crypt_release_keys(void) {
  int rc;
  int i;
  int keylen;

  ERR_free_strings();
  for(i=0;key2ipmsg_key_type[i]>=0;++i) {
    rc=pcrypt_get_rsa_key_length(key2ipmsg_key_type[i],&keylen);
    if (!rc)
      dbg_out("clear key length:%d\n",keylen);
    rc=pcrypt_store_rsa_key(key2ipmsg_key_type[i],NULL);
    if (rc)
      err_out("Can not store key: length: %d\n",keylen);
     
    if (rsa_keys[i]){
      RSA_free(rsa_keys[i]);
      rsa_keys[i]=NULL;
    }
  }

  return 0;
}

int
pcrypt_encrypt_message(const unsigned long peer_cap,const char *peer_e,const char *peer_n,const char *plain,size_t plain_length,char **ret_buff,unsigned long *akey_type){
  int rc;
  RSA *pubkey=NULL;
  BIGNUM *bn_encoded_key=NULL;
  size_t enc_len;
  size_t crypt_len;
  char *encrypted_key=NULL;
  char *encoded_key=NULL;
  char errbuf[1024];
  unsigned long key_type;

  if ( (!peer_e) || (!peer_n) || (!plain) || (!ret_buff) )
    return -EINVAL;

  rc=convert_peer_key(peer_e,peer_n,&pubkey);
  if (rc)
    return rc;

  rc=-ENOMEM;
  enc_len=RSA_size(pubkey);
  dbg_out("Public key:e=%s n=%s len=%d\n",
	  peer_e,
	  peer_n,
	  enc_len);

  encrypted_key=g_malloc(enc_len);
  if (!encrypted_key)
    goto free_pubkey_out;

  rc=RSA_public_encrypt(plain_length, plain, encrypted_key, pubkey, RSA_PKCS1_PADDING);
  if (rc<=0) {
    rc=ERR_get_error();
    err_out("Can not encrypt key with public key: err=%s\n", ERR_error_string(rc, errbuf));
    rc *= -1;
    goto free_encrypted_key_out;
  }
  rc=select_asymmetric_key(peer_cap,pubkey,&key_type);
  if (rc)
    goto free_encrypted_key_out;

  rc=-ENOMEM;
  bn_encoded_key=BN_bin2bn(encrypted_key,enc_len,NULL);
  if (!bn_encoded_key)
    goto free_encrypted_key_out;

  encoded_key=BN_bn2hex(bn_encoded_key);
  if (!encoded_key)
    goto free_bn_encoded_key_out;

  *ret_buff=encoded_key;
  *akey_type=key_type;

  rc=0;

 free_bn_encoded_key_out:
  if (bn_encoded_key)
    BN_free(bn_encoded_key);

 free_encrypted_key_out:
  if (encrypted_key)
    g_free(encrypted_key);

 free_pubkey_out:
  if (pubkey) {
    BN_free(pubkey->n);
    BN_free(pubkey->e);
    pubkey->n=NULL;
    pubkey->e=NULL;    
    RSA_free(pubkey);
  }
  return rc;
}

int
pcrypt_decrypt_message(unsigned long cap,const char *encrypted_skey,char **ret_buff,size_t *decrypted_len){
  int rc;
  RSA *privkey=NULL;
  char *decrypted_skey=NULL;
  size_t decrypted_skey_len;
  BIGNUM *bn_encrypted_key=NULL;
  size_t bn_encrypted_key_len;
  char *bin_skey=NULL;
  char errbuf[1024];

  if ( (!encrypted_skey) || (!ret_buff) || (!decrypted_len) )
    return -EINVAL;

  rc=pcrypt_crypt_refer_rsa_key(cap,&privkey);
  if (rc)
    return rc;

  rc=-ENOMEM;
  bn_encrypted_key=BN_new();
  if (!bn_encrypted_key)
    goto no_free_out;

  rc=BN_hex2bn(&bn_encrypted_key, encrypted_skey);
  if (!rc) {
    rc=ERR_get_error();
    err_out("Can not convert hex-key to BIGNUM: err=%s\n", ERR_error_string(rc, errbuf));
    rc=-rc;
    goto free_bn_encrypted_key_out;
  }
  
  bn_encrypted_key_len=BN_num_bytes(bn_encrypted_key);

  rc=-ENOMEM;
  bin_skey=g_malloc(bn_encrypted_key_len);
  if (!bin_skey)
    goto free_bn_encrypted_key_out;

  rc=BN_bn2bin(bn_encrypted_key,bin_skey);
  if (!rc) {
    rc=ERR_get_error();
    err_out("Can not convert BIGNUM:to binary err=%s\n", ERR_error_string(rc, errbuf));
    rc=-rc;
    goto free_bin_skey_out;
  }

  decrypted_skey_len=BN_num_bytes(privkey->n);

  rc=-ENOMEM;
  decrypted_skey=g_malloc(decrypted_skey_len);
  if (!decrypted_skey)
    goto free_bin_skey_out;

  rc=RSA_private_decrypt(bn_encrypted_key_len, bin_skey, decrypted_skey, privkey,
			 RSA_PKCS1_PADDING);

  if (rc<=0) {
    if (decrypted_skey)
      g_free(decrypted_skey);
    rc=ERR_get_error();
    err_out("Can not decrypt secret key err=%s\n", ERR_error_string(rc, errbuf));
    rc=-rc;
    goto free_bin_skey_out;
  }

  *decrypted_len=decrypted_skey_len;
  *ret_buff=decrypted_skey;
  rc=0;

 free_bin_skey_out:
  if (bin_skey)
    g_free(bin_skey);
 free_bn_encrypted_key_out:
  if (bn_encrypted_key)
    BN_free(bn_encrypted_key);
 no_free_out:
  return rc;
}
int 
pcrypt_sign(unsigned long cap,unsigned long sign_type,unsigned char *msg_str, unsigned char **ret_buff){
  int rc;
  RSA *key=NULL;
  unsigned char *out=NULL;
  size_t out_len;
  unsigned char hash[MD5SHA1_DIGEST_LEN];
  BIGNUM *bn_hash=NULL;
  unsigned char *hash_string=NULL;
  size_t msg_len;
  int nid_sign_type;
  size_t digest_len;
  char errbuf[1024];
  void *ref;

  if ( (!msg_str) || (!ret_buff) )
    return -EINVAL;

  rc=pcrypt_crypt_refer_rsa_key(cap,&key);
  if (rc)
    return rc;

  msg_len=strlen(msg_str);

  rc=-ENOMEM;
  out=g_malloc(RSA_size(key));
  if (!out)
    goto no_free_out;

  switch(sign_type) {
  case IPMSG_SIGN_MD5:
    nid_sign_type=NID_md5;
    digest_len=MD5_DIGEST_LEN;

    ref=MD5(msg_str, msg_len, hash);
    if (!ref) {
      rc=ERR_get_error();
      err_out("Can not calculate MD5 hash value: err=%s\n", ERR_error_string(rc, errbuf));
      rc=-rc;
      goto out_free_out;
    }
    break;
  case IPMSG_SIGN_SHA1:
    nid_sign_type=NID_sha1;
    digest_len=SHA1_DIGEST_LEN;

    ref=SHA1(msg_str, msg_len, hash);
    if (!ref) {
      rc=ERR_get_error();
      err_out("Can not calculate SHA1 hash value: err=%s\n", ERR_error_string(rc, errbuf));
      rc=-rc;
      goto out_free_out;
    }
    break;
  default:
    rc=-EINVAL;
    goto out_free_out;
    break;
  }

  rc=RSA_sign(nid_sign_type, hash, digest_len, out, &out_len, key);
  if (rc<0)
    goto out_free_out;
  
  rc=-ENOMEM;
  bn_hash=BN_new();
  if (!bn_hash)
    goto out_free_out;

  ref=BN_bin2bn(out, out_len, bn_hash);
  if (!ref) {
    rc=ERR_get_error();
    err_out("Can not convert binary key to BIGNUM: err=%s\n", ERR_error_string(rc, errbuf));
    rc=-rc;
    goto bn_hash_free_out;
  }


  hash_string=BN_bn2hex(bn_hash);
  if (!hash_string)
    goto bn_hash_free_out;

  rc=0;
  *ret_buff=hash_string;

 bn_hash_free_out:
  if (bn_hash)
    BN_free(bn_hash);  
 out_free_out:
  if (out)
    g_free(out);

 no_free_out:
  return rc;
}
int 
pcrypt_verify_sign(unsigned long cap,unsigned long sign_type,unsigned char *msg_str, 
		   unsigned char *sign,unsigned char *peer_e,unsigned char *peer_n){
  int rc;
  RSA *key=NULL;
  BIGNUM *bn_sign=NULL;
  unsigned char *raw_sign;
  size_t msg_len;
  unsigned char hash[MD5SHA1_DIGEST_LEN];
  int nid_sign_type;
  size_t digest_len;
  char errbuf[1024];
  void *ref;

  if ( (!msg_str) || (!sign) || (!peer_e) || (!peer_n) )
    return -EINVAL;

  rc=convert_peer_key(peer_e,peer_n,&key);
  if (rc)
    return rc;

  msg_len=strlen(msg_str);
  rc=-ENOMEM;
  /*
   *バイナリの署名を獲得
   */
  rc=BN_hex2bn(&bn_sign,sign);
  if (!rc) {
    rc=ERR_get_error();
    err_out("Can not convert hex-key to BIGNUM: err=%s\n", ERR_error_string(rc, errbuf));
    rc=-rc;
    goto bn_sign_free_out;
  }
  raw_sign=g_malloc(BN_num_bytes(bn_sign));
  if (!raw_sign)
    goto bn_sign_free_out;
  rc=BN_bn2bin(bn_sign, raw_sign);
  if (rc != RSA_size(key)) {
    err_out("hash size is invalid\n");
    goto raw_sign_free_out;
  }
  /*
   * タイミング攻撃対策
   */
  rc=RSA_blinding_on(key,NULL);
  if (!rc) {
    rc=ERR_get_error();
    err_out("Can not make RSA key blind: err=%s\n", ERR_error_string(rc, errbuf));
    rc=-rc;
    goto raw_sign_free_out;
  }

  switch(sign_type) {
  default:
    rc=-EINVAL;
    goto raw_sign_free_out;
    break;
  case IPMSG_SIGN_MD5:
    nid_sign_type=NID_md5;
    ref=MD5(msg_str, msg_len, hash);
    if (!rc) {
      rc=ERR_get_error();
      err_out("Can not calculate MD5 hash value : err=%s\n", ERR_error_string(rc, errbuf));
      rc=-rc;
      dbg_out("Verify with  MD5 sign\n");
      goto raw_sign_free_out;
    }
    digest_len=MD5_DIGEST_LEN;
    break;
  case IPMSG_SIGN_SHA1:
    nid_sign_type=NID_sha1;
    ref=SHA1(msg_str, msg_len, hash);
    if (!rc) {
      rc=ERR_get_error();
      err_out("Can not calculate SHA1 hash value : err=%s\n", ERR_error_string(rc, errbuf));
      rc=-rc;
      goto raw_sign_free_out;
    }
    digest_len=SHA1_DIGEST_LEN;
    dbg_out("Verify with  SHA1 sign\n");
    break;
  }

  rc=RSA_verify(nid_sign_type,hash,digest_len,raw_sign,RSA_size(key), key);
  if (!rc){
    rc=ERR_get_error();
    err_out("Sign invalid : err=%s\n", ERR_error_string(rc, errbuf));
    rc=-rc;
    goto raw_sign_free_out;
  }else{
    rc=0;
  }
 raw_sign_free_out:
  if (raw_sign)
    g_free(raw_sign);
 bn_sign_free_out:
  if (bn_sign)
    BN_free(bn_sign);
 free_key_out:
  if (key) {
    g_assert(key->n);
    g_assert(key->e);
    BN_free(key->n);
    BN_free(key->e);
    key->n=NULL;
    key->e=NULL;    
    RSA_free(key);
  }
 no_free_out:
  return rc;
}

int
pcrypt_get_key_filename(unsigned long key_name,char **pubkey,char **privkey) {
  int rc;
  size_t key_len,key_fname_len;
  RSA *rsa=NULL;
  size_t key_priv_fname_len;
  size_t key_pub_fname_len;
  char *pub_key_file=NULL;
  char *priv_key_file=NULL;
  int j;

  
  if ( (!pubkey) || (!privkey) )
    return -EINVAL;

  rc=pcrypt_get_rsa_key_length(key_name,&key_len);
  if (rc)
    return rc;

  dbg_out("Key bits:%d\n",
	  key_len);
  /* キー長をbit長で表した値の桁数を算出  */
  for(j=key_len,key_fname_len=1;j;j/=10,++key_fname_len);
  /* +2 is seperator and null terminate */
  key_priv_fname_len=strlen(RSA_PRIVKEY_FNAME_PREFIX) + key_fname_len+ 1;
  priv_key_file=g_malloc(key_priv_fname_len);
  if (!priv_key_file)
    return -EINVAL;

  key_pub_fname_len=strlen(RSA_PUBKEY_FNAME_PREFIX) + key_fname_len+ 1;
  pub_key_file=g_malloc(key_pub_fname_len);
  if (!pub_key_file)
    goto priv_key_file_free_out;

  snprintf(priv_key_file,key_priv_fname_len-1,"%s%d",
	   RSA_PRIVKEY_FNAME_PREFIX,
	   key_len);

  snprintf(pub_key_file,key_pub_fname_len-1,"%s%d",
	   RSA_PUBKEY_FNAME_PREFIX,
	   key_len);

  dbg_out("store files are Private-key:%s Public-key:%s\n",
	  priv_key_file,pub_key_file);
  
  *pubkey=pub_key_file;
  *privkey=priv_key_file;

  return 0;
 pub_key_file_free_out:
  if (pub_key_file)
    g_free(pub_key_file);
 priv_key_file_free_out:
  if (priv_key_file)
    g_free(priv_key_file);
  return rc;
}

int 
pcrypt_store_rsa_key(unsigned long key_name,pem_password_cb *cb){
  int rc;
  gchar *store_dir=NULL;
  char *home_dir=NULL;
  size_t key_len;
  RSA *rsa;
  int j;
  size_t key_fname_len;
  char *priv_key_file=NULL;
  char *pub_key_file=NULL;
  char *file_path=NULL;

  rc=get_envval("HOME",&home_dir);
  if (rc) 
    goto no_free_out;

  store_dir=g_build_filename(home_dir,G2IPMSG_KEY_DIR,NULL);
  if (!store_dir)
    goto home_dir_free_out;
  
  rc=check_secure_directory(store_dir);
  if ( (rc) && (rc != -ENOENT) ) {
    err_out("Directory %s is not secure.\n",store_dir);
    goto store_dir_free_out;
  }

  if (rc == -ENOENT) {
    dbg_out("Directory %s does not exist.\n",store_dir);
    rc=mkdir(store_dir,S_IRWXU);
    if (rc){
      rc=-errno;
      goto store_dir_free_out;
    }
  }
  dbg_out("Directory check:%s OK\n",store_dir);

  rc=pcrypt_crypt_refer_rsa_key(key_name,&rsa);
  if (rc)
    goto store_dir_free_out;

  rc=pcrypt_get_key_filename(key_name,&pub_key_file,&priv_key_file);
  if (rc)
    goto store_dir_free_out;

  /*
   * Private key
   */
  key_fname_len = 
    strlen(store_dir) + strlen(priv_key_file)+ 3;
  file_path=g_malloc(key_fname_len);
  if (!file_path)
    goto filename_free_out;

  snprintf(file_path, key_fname_len-1,"%s" G_DIR_SEPARATOR_S "%s",
	   store_dir,
	   priv_key_file);

  rc=store_private_key(file_path,rsa,cb);
  if (rc)
    goto filename_free_out;
  if (file_path)
    g_free(file_path);
  file_path=NULL;

  /*
   * Public key
   */
  key_fname_len = 
    strlen(store_dir) + strlen(pub_key_file)+ 3;
  file_path=g_malloc(key_fname_len);
  if (!file_path)
    goto filename_free_out;

  snprintf(file_path, key_fname_len-1,"%s" G_DIR_SEPARATOR_S "%s",
	   store_dir,
	   pub_key_file);

  rc=store_public_key(file_path,rsa);
  if (rc)
    goto filename_free_out;
  if (file_path)
    g_free(file_path);
  file_path=NULL;
  
  rc=0;

 filename_free_out:
  if (file_path)
    g_free(file_path);
 pub_key_file_free_out:
  if (pub_key_file)
    g_free(pub_key_file);
  if (priv_key_file)
    g_free(priv_key_file);
 store_dir_free_out:
  if (store_dir)
    g_free(store_dir);
 home_dir_free_out:
  if (home_dir)
    g_free(home_dir);
 no_free_out:
  return rc;
}

int 
pcrypt_load_rsa_key(unsigned long key_name,pem_password_cb *cb){
  int rc;
  gchar *store_dir=NULL;
  char *home_dir=NULL;
  size_t key_len;
  RSA *rsa=NULL;
  int j;
  size_t key_fname_len;
  char *priv_key_file=NULL;
  char *pub_key_file=NULL;
  char *file_path=NULL;
  char errbuf[1024];

  rc=get_envval("HOME",&home_dir);
  if (rc) 
    goto no_free_out;

  store_dir=g_build_filename(home_dir,G2IPMSG_KEY_DIR,NULL);
  if (!store_dir)
    goto home_dir_free_out;
  
  rc=check_secure_directory(store_dir);
  if  (rc) {
    err_out("Directory %s is not secure or does not exist.\n",store_dir);
    goto store_dir_free_out;
  }

  dbg_out("Directory check:%s OK\n",store_dir);

  rc=pcrypt_get_key_filename(key_name,&pub_key_file,&priv_key_file);
  if (rc)
    goto store_dir_free_out;

  /*
   * Private key
   */
  key_fname_len = 
    strlen(store_dir) + strlen(priv_key_file)+ 3;
  file_path=g_malloc(key_fname_len);
  if (!file_path)
    goto filename_free_out;

  snprintf(file_path, key_fname_len-1,"%s" G_DIR_SEPARATOR_S "%s",
	   store_dir,
	   priv_key_file);

  rc=load_private_key(file_path,&rsa,cb);
  if (rc)
    goto filename_free_out;
  if (file_path)
    g_free(file_path);
  file_path=NULL;

  /*
   * Public key
   */
  key_fname_len = 
    strlen(store_dir) + strlen(pub_key_file)+ 3;
  file_path=g_malloc(key_fname_len);
  if (!file_path)
    goto filename_free_out;

  snprintf(file_path, key_fname_len-1,"%s" G_DIR_SEPARATOR_S "%s",
	   store_dir,
	   pub_key_file);

  rc=load_public_key(file_path,&rsa);
  if (rc)
    goto filename_free_out;
  if (file_path)
    g_free(file_path);
  file_path=NULL;

  rc=RSA_check_key(rsa);
  if (!rc) {
    rc=ERR_get_error();
    err_out("Invalid RSA key : err=%s\n", ERR_error_string(rc, errbuf));
    rc=-rc;
    RSA_free(rsa);
    goto filename_free_out;
  }

  rc=pcrypt_crypt_set_rsa_key(key_name,rsa);
  if (rc) {
    RSA_free(rsa);
    goto filename_free_out;
  }
  rc=pcrypt_get_rsa_key_length(key_name,&key_len);
  g_assert(!rc);
  dbg_out("RSA key length: %d has been loaded successfully.\n",key_len);
  rc=0;

 filename_free_out:
  if (file_path)
    g_free(file_path);
 pub_key_file_free_out:
  if (pub_key_file)
    g_free(pub_key_file);
  if (priv_key_file)
    g_free(priv_key_file);
 store_dir_free_out:
  if (store_dir)
    g_free(store_dir);
 home_dir_free_out:
  if (home_dir)
    g_free(home_dir);
 no_free_out:
  return rc;
}
int 
select_asymmetric_key(unsigned long peer_cap,RSA *rsa,unsigned long *selected_key){
  unsigned long candidates;
  int i;
  int rc;
  int added_num=1;
  int first=0;
  int key_type;
  size_t size_in_bit;

  if ( (!selected_key) || (!rsa) )
    return -EINVAL;

  candidates=(peer_cap & hostinfo_get_ipmsg_crypt_capability());
  g_assert(candidates);
  
  size_in_bit=RSA_size(rsa)*8;
  dbg_out("key size=%d.\n",size_in_bit);
  switch(size_in_bit){
  case 512:
    dbg_out("This is 512 bit key.\n");
    key_type=IPMSG_RSA_512;
    break;
  case 1024:
    dbg_out("This is 1024 bit key.\n");
    key_type=IPMSG_RSA_1024;
    break;
  case 2048:
    dbg_out("This is 2048 bit key.\n");
    key_type=IPMSG_RSA_2048;
    break;
  default:
    rc=-ENOENT;
    return rc;
    break;
  }
  
  if (!(key_type & candidates))
    return -EINVAL;

  *selected_key=key_type;
  dbg_out("Selected key name in ipmsg:0x%x\n",key_type);

  return 0;
}
int 
select_signature(unsigned long peer_cap,unsigned long *selected_algo,int speed){
  if (!selected_algo)
    return -EINVAL;

  if (!(peer_cap & SIGN_CAPS))
    return -ENOENT;

  if (peer_cap & IPMSG_SIGN_MD5)
    *selected_algo=IPMSG_SIGN_MD5;

  if (peer_cap & IPMSG_SIGN_SHA1)
    *selected_algo=IPMSG_SIGN_SHA1;

  return 0;
}
